home *** CD-ROM | disk | FTP | other *** search
- {
- Hi,
- here's another contribution - an LFN unit which is actually useful. It
- allows working with near-normal TP/TPW commands, transparently on LFN
- and non-LFN disks. Enjoy.
-
- Eyal Doron
- }
-
- {$IFDEF WINDOWS}
- {$N-,V-,W-,G+}
- {$ELSE}
- {N-,V-,G+}
- {$ENDIF}
-
- Unit lfnunit;
-
- {========================================================================}
- { LFNUnit - A long filename support unit for TP6 and TPW1.5. }
- { Written by Eyal Doron, doron@physics,technion.ac.il, June 1997. }
- { Released into the public domain. }
- { }
- { This is a unit to support long filenames in Win95 and WinNT, for use }
- { in ordinary 16-bit programs in Turbo Pascal 6.0 and Turbo Pascal for }
- { Windows 1.5. It should be a simple matter to adapt to TP/BP 7 as well. }
- { The unit is built to support LFN if available, and the usual FAT16 }
- { format if not, in a transparent manner, i.e. the programmer should not }
- { worry whether LFN is supported or not, the routines work the same in }
- { both cases. The unit is not complete, in the sense that not all of the }
- { interrupts are supported, but the main thrust is the enhancement of }
- { the Turbo Pascal I/O scheme to support LFN in as natural a way as }
- { possible. }
- { }
- { The unit contains three families of procedures and functions: }
- { 1) Basic LFN API: This is a set of procedures and functions that give }
- { access to the LFN interrupts: FindFirst/Next/Close, short and long }
- { names, time, attributes and creation. }
- { 2) Service routines. These routines make use of the LFN API to mimic }
- { the operation of the DOS or WinDos supplied routines, only with LFN }
- { support. I chose to mimic the TP6.0 routines, rather than the TPW }
- { ones, because I prefer Pascal-type strings to C-type strings, but }
- { its a simple matter to add these as well. All the routines return }
- { their error codes inside the DOS/WinDos global "DosError". }
- { 3) Input-output support. This set of procedures and functions defines }
- { the paradigm for LFN support in Turbo Pascal. It "rides" on top of }
- { the usual file variables ("file", "file of" and "text"), and stores }
- { the additional LFN info in the UserData field of the file records. }
- { The routines have an interface which is similar to the TP ones, }
- { namely "LFNAssign" is equivalent to "Assign", "LFNRewrite" to }
- { "Rewrite", and so on. This paradigm enables use of the usual Pascal }
- { I/O scheme and routines with long file names, in an almost }
- { transparent manner. The differences are: }
- { a) Before using a file variable it has to be initialized by calling }
- { LFNNew. After you are done with it, you should call LFNDispose }
- { to free the allocated memory. }
- { b) You MUST consistently use LFNNew, LFNAssign, LFNRewrite, }
- { LFNRename and LFNDispose in order to support LFN. The other }
- { routines are optional, providing error detection and consistent }
- { error trapping, but the TP equivalents should also work. }
- { c) LFNReset, LFNRewrite and LFNAppend always accept a RecLen }
- { parameter, which is optional in Reset and Rewrite and missing in }
- { Append. This is because TP does not support overloading. The }
- { parameter is ignored for text files and when it is zero. }
- { d) LFNAppend differs from Append also in that if the file does not }
- { exist, Append reports a DOS error, while LFNAppend creates it }
- { using LFNRewrite. }
- { e) LFNFindFirst/LFNFindNext return the name as an AsciiZ string, }
- { not a Pascal string, even in TP6, for the sake of consistency. }
- { f) All the routines return the error code in the DosError global }
- { Dos/WinDos variable, and most of them also return it as a }
- { functional result. Additionally, the "LFNRuntimeErrors" global }
- { boolean variable controls the generation of runtime errors. }
- { }
- { Comments, bug reports, etc. are welcome. }
- {========================================================================}
-
-
- Interface
-
- Uses
- {$IFDEF WINDOWS}
- WinDos,WObjects,WinTypes,WinProcs,strings;
- {$ELSE}
- Dos,Objects;
- {$ENDIF}
-
- const
- ShortPathName = 79;
- LFNRuntimeErrors: boolean = false; { Determines if runtime errors are generated }
-
- LFNErr_Uninitialized = 120; { LFN routines called before LFNAssign }
- LFNErr_NotAllocated = 121; { LFN routines called before LFNNew }
- LFNErr_NotATextFile = 122; { Appending to a non-text file }
-
- {$IFDEF WINDOWS}
- ofn_LongNames = $00200000; { Required to support LFN in the common dialogs. }
- { OR it into the Flags record of TOpenFilename. }
- {$ENDIF}
-
- type
- ShortPathStr = string[ShortPathName];
- {$IFNDEF WINDOWS}
- TSearchRec = SearchRec;
- TDateTime = DateTime;
- PChar = ^Char;
- {$ENDIF}
-
- TLFNSearchRec = record
- Attr : longint;
- Creation : comp;
- LastAccess : comp;
- LastMod : comp;
- HighFileSize : longint; { high 32 bits }
- Size : longint; { low 32 bits }
- Reserved : comp;
- Name : array[0..259] of char;
- ShortName : array[0..13] of char;
- Handle : word;
- end;
- PLFNSearchRec = ^TLFNSearchRec;
- { Form used for old-style searches, with an embedded TSearchRec }
- TLFNShortSearchRec = record
- Attr : longint;
- Creation : comp;
- LastAccess : comp;
- LastMod : comp;
- HighFileSize : longint;
- Size : longint;
- Reserved : comp;
- Name : array[0..13] of char;
- SRec : TSearchRec;
- Filler : array[1..260-14-sizeof(TSearchRec)] of byte;
- ShortName : array[0..13] of char;
- Handle : word;
- end;
- PLFNShortSearchRec = ^TLFNShortSearchRec;
-
- { A record to isolate the UserData parameters }
- TLFNFileParam = record
- Handle : word; { The file handle }
- Mode : word; { The file mode }
- Res1 : array[1..28] of byte; { Everything else up to UserData }
- { Begin UserData }
- lfname : PString; { The long filename in String form }
- plfname : PChar; { The long filename in AsciiZ form }
- TextFile : boolean; { Is it a text or binary file }
- Initialized: boolean; { Has it been LFNAssigned }
- Magic : string[3]; { ID to check LFNNew }
- Res2 : array[0..1] of byte; { 2 bytes left in UserData }
- { End UserData }
- SName : array[0..79] of char; { The short filename }
- end;
- PLFNFileParam = ^TLFNFileParam;
-
- var
- LFNAble: boolean; { Is LFN supported or not. Upon startup it is determined }
- { by the OS, but can be switched off later if need be. }
-
- function LFNToggleSupport(on: boolean): boolean;
-
- {$IFNDEF WINDOWS}
- { I need these to access the Srec.Name field properly }
- function PCharOf(var F): Pchar;
- function StrPas(P: PChar): string;
- {$ENDIF}
-
- function PChar2Pstring(F: Pchar): PString;
- function PString2PChar(F: Pstring): PChar;
-
- { Basic API calls }
- function LFNTimeToDos(var LTime: comp): longint;
- function DosTimeToLFN(var Time: longint; var LTime: comp): word;
- function LGetAttr(Filename: PChar; var Attr: word): word;
- function LRenameFile(FromName,ToName: PChar): word;
- function LCreateEmpty(fname: PChar): word;
- function LFNFindFirst(filespec: string; attr: word; var S: TLFNSearchRec): word;
- function LFNFindNext(var S: TLFNSearchRec): word;
- function LFNFindClose(var S: TLFNSearchRec): word;
- function LFNShortName(LongName: string): ShortPathStr;
- function LFNLongName(ShortName: ShortPathStr): string;
-
- { Service routines }
- procedure LFNUnpackTime(var LTime: comp; var DT: TDateTime);
- function LFNGetFAttr(var F; var Attr: word): integer;
- function LFNFileExist(fname: string): boolean;
- function LFNFSearch(Path,DirList: string): string;
- procedure LFNFSplit(Path: string; Dir,Name,Ext: PString);
- function LFNFExpand(Path: string): string;
- procedure CanonicalFname(var S: string);
- function CanonicalFilename(Fname: PChar): Pchar;
-
- { Interface to the Pascal Input/Output routines }
- procedure LFNNew (var F; IsText: boolean);
- function LFNAssign (var F; name: string): integer;
- function LFNRewrite(var F; RecLen: word): integer;
- function LFNAppend (var F; RecLen: word): integer;
- function LFNReset (var F; RecLen: word): integer;
- function LFNErase (var F): integer;
- function LFNClose (var F): integer;
- procedure LFNDispose(var F);
- function LFNRename (var F; NewName: string): integer;
-
-
- implementation
-
- const
- {$IFNDEF WINDOWS}
- faReadOnly = ReadOnly;
- faHidden = Hidden;
- faSysFile = SysFile;
- faVolumeID = VolumeID;
- faDirectory = Directory;
- faArchive = Archive;
- faAnyFile = AnyFile;
- {$ENDIF}
-
- LFNMagic = 'LFN';
-
- type
- PSearchRec = ^TSearchRec;
- TByteArray = array[0..$FFF0-1] of char;
- PByteArray = ^TByteArray;
-
- {$IFNDEF WINDOWS}
- function PCharOf(var F): Pchar;
- { A very simple function which returns a pointer to its argument. }
- { Its main use is in turning array[...] of char in to PChar, to }
- { simulate the TPW/TP7/BP7 extended syntax. }
- begin
- PCharOf:=@F;
- end;
-
- function StrPas(P: PChar): string;
- var
- i: integer;
- tmp: PString;
- begin
- New(tmp); tmp^:=''; if P=Nil then Exit;
- i:=0;
- while (length(tmp^)<256) and (PByteArray(P)^[i]<>#0) do
- begin
- tmp^:=tmp^+PByteArray(P)^[i]; inc(i);
- end;
- StrPas:=tmp^; Dispose(tmp);
- end;
-
- function StrLen(P: PChar): integer;
- var
- i: integer;
- begin
- i:=0;
- if P<>Nil then while (i<$7FFF) and (PByteArray(P)^[i]<>#0) do inc(i);
- StrLen:=i;
- end;
- {$ENDIF}
-
- function PChar2Pstring(F: Pchar): PString;
- { This routine changes a PChar (AsciiZ) string to a }
- { Pascal-type string, in the same memory location. }
- var
- i,len: integer;
- begin
- len:=StrLen(F); if len>255 then len:=255;
- for i:=len downto 1 do PByteArray(F)^[i]:=PByteArray(F)^[i-1];
- F^:=Chr(len);
- PChar2PString:=PString(F);
- end; { PChar2Pstring }
-
- function PString2PChar(F: Pstring): PChar;
- { This routine changes a Pascal-type string to an }
- { AsciiZ string, in the same memory location. }
- var
- i,len: integer;
- begin
- len:=length(F^);
- for i:=1 to len do F^[i-1]:=F^[i]; F^[len]:=#0;
- PString2PChar:=PChar(F);
- end; { PString2PChar }
-
- {$IFDEF WINDOWS}
- function SupportsLFN: boolean;
- var
- WinVersion: word;
- begin
- { SupportsLFN:=false; Exit;}
- WinVersion := LoWord(GetVersion);
- SupportsLFN:=true;
- If ((Lo(WinVersion) = 3) and {windows 95 first}
- (Hi(WinVersion) < 95)) or {version is 3.95 }
- (Lo(WinVersion) < 3) then SupportsLFN := False;
- end;
- {$ELSE}
- function SupportsLFN: boolean; assembler;
- asm
- mov ax, $160a
- int $2f
- cmp ax, 0
- jne @no { Not running under Windows }
- cmp bh, 2
- jle @no { Major version <3 }
- cmp bh, 4
- jge @yes { Major version >3 }
- cmp bl, 94
- jle @no { Major version =3, minor <95 }
- @yes:
- mov al, true
- jmp @exit
- @no:
- mov al, false
- @exit:
- end; { SupportsLFN }
- {$ENDIF}
-
- function LFNToggleSupport(on: boolean): boolean;
- { This routine toggles LFN support on and off, provided }
- { the OS supports it. It returns the previous status. }
- begin
- LFNToggleSupport:=LFNAble;
- LFNAble:=on and SupportsLFN;
- end;
-
- {==============================================================}
- { BASIC LFN API CALLS. }
- { This is a set of routines which implement the WIn95 LFN API, }
- { in Turbo Pascal form. }
- {==============================================================}
-
- function LFNTimeToDos(var LTime: comp): longint; assembler;
- { Convert 64-bit number of 100ns since 01-01-1601 UTC to local DOS format time}
- { (LTime is var to avoid putting it on the stack) }
- asm
- push ds
- lds si,LTime
- xor bl,bl
- mov ax,71a7h
- int 21h
- pop ds
- mov ax,cx
- cmc
- sbb cx,cx
- and ax,cx
- and dx,cx
- end; { LFNTimeToDos }
-
- function DosTimeToLFN(var Time: longint; var LTime: comp): word;
- { Convert DOS time to the 64-bit Win95 format }
- var
- DosTime,DosDate: word;
- DT: TDateTime;
- begin
- UnpackTime(Time,DT); FillChar(LTime,sizeof(LTime),0);
- with DT do
- begin
- DosTime:=(sec div 2) or (min shl 5) or (hour shl 11);
- DosDate:=day or (Month shl 5) or ((Year-1980) shl 9);
- end;
- asm
- mov ax, $71A7
- mov bl, 1
- mov cx, DosTime
- mov dx, DosDate
- mov bh, 0
- les di, LTime
- int $21
- jnc @1
- mov [DosError],ax
- @1:
- end;
- DosTimeToLFN:=DosError;
- end; { DosTimeToLFN }
-
- function LGetAttr(Filename: PChar; var Attr: word): word; assembler;
- { Get the attributes of a file, PChar syntax }
- asm
- push ds
- lds dx,Filename
- mov ax,7143h
- xor bl,bl
- int 21h
- pop ds
- les di,Attr
- mov es:[di],cx
- sbb bx,bx
- and ax,bx
- mov [DosError],ax
- end; { LGetAttr }
-
- function LFindFirst(FileSpec: pchar; Attr: word; var SRec: TLFNSearchRec): word;
- assembler;
- { Search for files }
- asm
- push ds
- lds dx,FileSpec
- les di,SRec
- mov cx,Attr
- xor si,si
- mov ax,714eh
- int 21h
- pop ds
- sbb bx,bx
- mov es:[di].TLFNSearchRec.Handle,ax
- and ax,bx
- mov [DosError],ax
- end;
-
- function LFindNext(var SRec: TLFNSearchRec): word; assembler;
- { Find next file }
- asm
- mov ax,714fh
- xor si,si
- les di,SRec
- mov bx,es:[di].TLFNSearchRec.Handle
- int 21h
- sbb bx,bx
- and ax,bx
- mov [DosError],ax
- end;
-
- function LFindClose(var SRec: TLFNSearchRec): word; assembler;
- { Free search handle }
- asm
- mov ax,714fh
- mov bx,es:[di].TLFNSearchRec.Handle
- int 21h
- sbb bx,bx
- and ax,bx
- mov [DosError],ax
- end;
-
- function LGetShortName(FileName: pchar; Result: pchar): word; assembler;
- { Return complete short name/path for input file/path in buffer }
- { Result (79 bytes) }
- asm
- push ds
- lds si,FileName
- les di,Result
- mov ax,7160h
- mov cx,1
- int 21h
- pop ds
- sbb bx,bx
- and ax,bx
- mov [DosError],ax
- end;
-
- function LGetLongName(FileName: PChar; Result: PChar): word; assembler;
- { Return complete long name/path for input file/path in buffer }
- { Result (261 bytes) }
- asm
- push ds
- lds si,FileName
- les di,Result
- mov ax,7160h
- mov cx,2
- int 21h
- pop ds
- sbb bx,bx
- and ax,bx
- mov [DosError],ax
- end;
-
- function LRenameFile(FromName,ToName: PChar): word; assembler;
- { Rename a file, supports long filenames. }
- asm
- push ds
- mov ax, $7156
- lds dx, FromName
- les di, ToName
- int $21
- jc @1
- mov ax, 0
- @1:
- pop ds
- mov [DosError],ax
- end; { LRenameFile }
-
- function LCreateEmpty(fname: PChar): word; assembler;
- { Create an empty file with the given (long) name. }
- asm
- push ds
- mov ax, $716C
- mov bx, 000010b { Open long file name for writing }
- mov cx, 0
- mov dx, 10001b { Open if exists, create of not. }
- lds si, fname
- mov di, 0
- int $21
- jc @1 { error creating file }
- mov bx, ax { ok, close it again }
- mov ah, $3E
- int $21
- jc @1 { error closing file }
- mov ax, 0 { ok, return zero }
- @1:
- pop ds
- mov [DosError],ax
- end; { LCreateEmpty }
-
- { Pascal-string based interface routines }
-
- function LFNFindFirst(filespec: string; attr: word; var S: TLFNSearchRec): word;
- { Implement the FindFirst procedure. This routine will call the TP }
- { FindFirst if LFN is not supported, and will translate the result }
- { into the TLFNSearchRec variable. }
- { NOTE: Under Win95, the filespec will be checked against both the }
- { long and the short filenames, so an additional check may be }
- { necessary. }
- begin
- If LFNAble then
- begin
- filespec := filespec + #0;
- LFindFirst(PChar(@Filespec[1]),Attr,S);
- if (DosError=0) and (S.shortname[0]=#0) then
- begin
- move(S.name,S.shortname,sizeof(S.shortname)-1);
- S.shortname[sizeof(S.shortname)-1]:=#0;
- end;
- end else
- begin
- FillChar(S,sizeof(S),0);
- {$IFDEF WINDOWS}
- FileSpec:=FileSpec+#0;
- FindFirst(PChar(@FileSpec[1]),Attr,PLFNShortSearchRec(@S)^.SRec);
- {$ELSE}
- FindFirst(FileSpec,Attr,PLFNShortSearchRec(@S)^.SRec);
- {$ENDIF}
- if DosError=0 then
- begin
- {$IFDEF WINDOWS}
- Move(PLFNShortSearchRec(@S)^.SRec.name,S.Name,13); S.name[13]:=#0;
- {$ELSE}
- FillChar(S.Name,14,0);
- Move(PLFNShortSearchRec(@S)^.SRec.name[1],S.Name,
- byte(PLFNShortSearchRec(@S)^.SRec.name[0]));
- {$ENDIF}
- DosTimeToLFN(PLFNShortSearchRec(@S)^.SRec.Time,S.LastMod);
- S.Attr:=PLFNShortSearchRec(@S)^.SRec.Attr;
- S.Size:=PLFNShortSearchRec(@S)^.SRec.Size;
- end;
- end;
- LFNFindFirst:=DosError;
- end; { LFNFindFirst }
-
- function LFNFindNext(var S: TLFNSearchRec): word;
- { Implement the FindNext procedure. This routine will call the TP }
- { FindNext if LFN is not supported, and will translate the result }
- { into the TLFNSearchRec variable. }
- { NOTE: Under Win95, the filespec will be checked against both the }
- { long and the short filenames, so an additional check may be }
- { necessary. }
- begin
- If LFNAble then
- begin
- LFindNext(S);
- if (DosError=0) and (S.shortname[0]=#0) then
- begin
- move(S.name,S.shortname,sizeof(S.shortname)-1);
- S.shortname[sizeof(S.shortname)-1]:=#0;
- end;
- end else
- begin
- FindNext(PLFNShortSearchRec(@S)^.SRec);
- if DosError=0 then
- begin
- {$IFDEF WINDOWS}
- Move(PLFNShortSearchRec(@S)^.SRec.name,S.Name,13); S.name[13]:=#0;
- {$ELSE}
- FillChar(S.Name,14,0);
- Move(PLFNShortSearchRec(@S)^.SRec.name[1],S.Name,
- byte(PLFNShortSearchRec(@S)^.SRec.name[0]));
- {$ENDIF}
- DosTimeToLFN(PLFNShortSearchRec(@S)^.SRec.Time,S.LastMod);
- S.Attr:=PLFNShortSearchRec(@S)^.SRec.Attr;
- S.Size:=PLFNShortSearchRec(@S)^.SRec.Size;
- end;
- end;
- LFNFindNext:=DosError;
- end; { LFNFindNext }
-
- function LFNFindClose(var S: TLFNSearchRec): word;
- { Close the Win95 TLFNSearchRec structure. if LFN is not suppported, }
- { this routine does nothing. }
- begin
- If LFNAble then LFNFindClose:=LFindClose(S)
- else LFNFindClose:=0;
- end; {function}
-
- function LFNShortName(LongName: string): ShortPathStr;
- { Returns the short name of the specified file. If LFN is not }
- { supported, returns the input filename. }
- var
- P,Q: PChar;
- i,len: integer;
- begin
- if not LFNAble then
- begin
- LFNShortName:=LongName; Exit;
- end;
- len:=length(LongName);
- for i:=1 to len do LongName[i-1]:=LongName[i]; LongName[len]:=#0;
- P:=@Longname;
- GetMem(Q,270); Q^:=#0;
- if LGetShortName(P,Q)=0 then
- begin
- if Q^=#0 then LFNShortName:=LongName
- else LFNShortName:=StrPas(Q);
- end else LFNShortName:='';
- FreeMem(Q,270);
- end; { ShortName }
-
- function LFNLongName(ShortName: ShortPathStr): string;
- { Returns the long name of the specified file. If LFN is not }
- { supported, returns the input filename. }
- var
- SRec: PLFNSearchRec;
- P: PChar;
- P0,D,N,E: PString;
- i,len: integer;
- begin
- LFNLongName:=ShortName; if not LFNAble then Exit;
- len:=length(ShortName); if len=0 then Exit;
- New(D); LFNFSplit(ShortName,D,Nil,Nil);
- for i:=1 to len do ShortName[i-1]:=ShortName[i]; ShortName[len]:=#0;
- GetMem(P0,270); P:=@PByteArray(P0)^[1]; P0^:=''; P^:=#0;
- LGetLongName(PChar(@ShortName),P); PByteArray(P)^[256]:=#0;
- P0^[0]:=Chr(StrLen(P));
- Dispose(D);
- if P^=#0 then LFNLongName:=ShortName
- else LFNLongName:=StrPas(P);
- FreeMem(P0,270);
- end; { LFNLongName }
-
- {====================================================================}
- { DERIVATIVE SERVICE ROUTINES. }
- { This is a set of routines which mimic, as closely as possible, the }
- { equivalent routines in Turbo Pascal, except that they support }
- { long filenames. In many cases, they are drop-in replacements, but }
- { some are new. }
- {====================================================================}
-
- procedure LFNUnpackTime(var LTime: comp; var DT: TDateTime);
- { Convert 64-bit time to date/time record }
- begin
- UnpackTime(LFNTimeToDos(LTime),DT);
- end;
-
- function LFNGetFAttr(var F; var Attr: word): integer;
- { Get the attributes of a file, using its File variable. }
- { The file should have been LFNAssign'ed first. Its not }
- { strictly required, except for error checking. }
- { Returns the DOS error code. }
- begin
- LFNGetFAttr:=0; DosError:=0;
- with PLFNFileParam(@F)^ do
- if (Magic<>LFNMagic) or (not Initialized) then
- begin
- DosError:=2; LFNGetFAttr:=2; Exit;
- end;
- GetFAttr(F,Attr); LFNGetFAttr:=DosError;
- end; { LFNGetFAttr }
-
- function LFNFileExist(fname: string): boolean;
- { Returns TRUE if the file exists, and FALSE otherwise. }
- var
- fl: file;
- attr,i,len: word;
- P: PChar;
- begin
- if fName='' then
- begin
- LFNFileExist:=false; Exit;
- end;
- if LFNAble then
- begin
- len:=length(fname); for i:=1 to len do fname[i-1]:=fname[i];
- fname[len]:=#0; LGetAttr(PChar(@fname),Attr)
- end else
- begin
- Assign(fl,fname); GetFAttr(fl,Attr);
- end;
- LFNFileExist:=(DosError=0);
- end; { LFNFileExist }
-
- function LFNFSearch(Path,DirList: string): string;
- { Search for a file in a semicolon-delimited list of directories. }
- { This is a drop-in replacement for FSearch (TP6), which I }
- { personally find more useful than the later FileSearch. }
- var
- i,len,Ind: integer;
- which: PChar;
- tmp: PString;
- found: boolean;
- begin
- LFNFSearch:=''; if Path='' then Exit;
- if LFNAble then
- begin
- if (DirList='') and not LFNFileExist(Path) then Exit;
- if DirList='' then
- begin
- LFNFSearch:=Path; Exit;
- end;
- Ind:=1; New(tmp); found:=false;
- while (DirList<>'') and (DirList[1]=';') do delete(DirList,1,1);
- repeat
- tmp^:='';
- while (Ind<=length(DirList)) and (DirList[Ind]<>';') do
- begin
- tmp^:=tmp^+DirList[Ind]; inc(Ind);
- end;
- while (Ind<=length(DirList)) and (DirList[Ind]=';') do inc(Ind);
- if Ind>length(DirList) then Ind:=0 else inc(Ind);
- if tmp^<>'' then
- begin
- if tmp^[length(tmp^)]<>'\' then tmp^:=tmp^+'\';
- if LFNFileExist(tmp^+Path) then
- begin
- LFNFSearch:=LFNFExpand(tmp^+Path); found:=true;
- end;
- end;
- until found or (Ind=0);
- Dispose(tmp);
- end else
- begin
- {$IFDEF WINDOWS}
- GetMem(Which,256);
- len:=length(Path); for i:=1 to len do Path[i-1]:=Path[i]; Path[len]:=#0;
- len:=length(DirList); for i:=1 to len do DirList[i-1]:=DirList[i]; DirList[len]:=#0;
- FileSearch(which,PChar(@Path),PChar(@DirList));
- LFNFSearch:=StrPas(Which); FreeMem(Which,256);
- {$ELSE}
- LFNFSearch:=FSearch(Path,DirList);
- {$ENDIF}
- end;
- end; { LFNFSearch }
-
- procedure LFNFSplit(Path: string; Dir,Name,Ext: PString);
- { An almost drop-in replacement for the TP6 FSplit, which supports LFN. }
- { The additional difference is that the arguments are passed as pointers, }
- { rather than VAR variables. This is so that if a file segment is not }
- { needed, one can pass NIL in the respective variable, and it will not }
- { be returned. }
- var
- StrPt,StrSlash,StrEnd: integer;
- begin
- StrEnd:=length(Path);
- StrPt:=StrEnd; StrSlash:=0;
- while(StrPt>0) and (Path[StrPt]<>'.') and (Path[StrPt]<>'\') do dec(StrPt);
- if (StrPt>0) and (Path[StrPt]='.') then { found extension }
- begin
- StrSlash:=StrPt-1;
- while (StrSlash>0) and (Path[StrSlash]<>'\') do dec(StrSlash);
- end else if (StrPt>0) and (Path[StrPt]='\') then { No extension }
- begin
- StrSlash:=StrPt; StrPt:=StrEnd+1;
- end else if StrPt=0 then { All name }
- begin
- StrPt:=StrEnd+1; StrSlash:=0;
- end;
-
- if Dir<>Nil then
- begin
- Dir^:='';
- if StrSlash>0 then Dir^:=Copy(Path,1,StrSlash);
- end;
- if Name<>Nil then
- begin
- Name^:='';
- if StrPt>StrSlash+1 then Name^:=Copy(Path,StrSlash+1,StrPt-StrSlash-1);
- end;
- if Ext<>Nil then
- begin
- Ext^:='';
- if StrPt<=StrEnd then Ext^:=Copy(Path,StrPt,255);
- end;
- end; { LFNFSplit }
-
- function LFNFExpand(Path: string): string;
- { Drop-in replacement for the TP6 FExpand, which supports LFN. }
- { Personally, I prefer it to the later FileExpand. }
- var
- D,N,E,P: PString;
- i,j,ndots: integer;
- begin
- for i:=1 to length(Path) do if Path[i]='/' then Path[i]:='\';
- LFNFExpand:='';
- GetMem(P,270);
- {$IFDEF WINDOWS}
- FileExpand(PChar(P)+1,'.'); P^[0]:=chr(StrLen(PChar(P)+1));
- {$ELSE}
- P^:=FExpand('.');
- {$ENDIF}
- if (P^<>'') and (P^[length(P^)]<>'\') then P^:=P^+'\';
- P^:=LFNLongName(P^);
- ndots:=0;
- while (ndots<length(Path)) and (Path[Ndots+1]='.') do inc(ndots);
- if (length(Path)>1) and (UpCase(Path[1]) in ['A'..'Z']) and (Path[2]=':') then
- P^:=Path { Fully qualified }
- else if Path[1]='\' then { Only drive missing }
- P^:=Copy(P^,1,2)+Path
- else begin
- for i:=1 to ndots-1 do { relative filenames, multiple dots }
- begin
- if length(P^)>3 then
- begin
- j:=length(P^)-1;
- while (j>3) and (P^[j]<>'\') do dec(j);
- P^[0]:=Chr(j);
- end;
- delete(Path,1,1);
- end;
- if Pos('.\',Path)=1 then Delete(Path,1,2)
- else if Pos('.',Path)=1 then Delete(Path,1,1);
- P^:=P^+Path;
- end;
- LFNFExpand:=P^;
- FreeMem(P,270);
- end; { LFNFExpand }
-
- procedure CanonicalFname(var S: string);
- { This routine takes a filename and changes its case to a canonical form: }
- { 1. Without LFN support, lowercase. }
- { 1. For existing short filenames, or dir names, lowercase. }
- { 2. For existing long filenames, the system-supplied case. }
- { 3. For non-existing filenames, expand the existing part of the path, }
- { and leave the rest unchanged. }
- { In all cases '/' is changed to '\'. }
- type
- TBf = array[1..3] of string;
- var
- lname,sname,res: Pstring;
- Buf: ^TBf;
- i,j: integer;
- exists: boolean;
-
- procedure StrLwr(var L: string);
- var
- i: integer;
- begin
- for i:=1 to length(L) do if L[i] in ['A'..'Z'] then
- L[i]:=Chr(Ord(L[i])-Ord('A')+Ord('a'));
- end;
-
- begin
- for i:=1 to length(S) do if S[i]='/' then S[i]:='\';
- if LFNAble then
- begin
- New(Buf);
- Buf^[1]:='';
- repeat
- i:=Pos('\',S); if i=0 then i:=length(S);
- if S[i]='\' then exists:=LFNFileExist(Buf^[1]+Copy(S,1,i)+'.')
- else exists:=LFNFileExist(Buf^[1]+Copy(S,1,i));
- if exists then
- begin
- Buf^[2]:=LFNShortName(Buf^[1]+Copy(S,1,i));
- Buf^[3]:=LFNLongName(Buf^[2]);
- j:=length(Buf^[2])-1; while (j>0) and (Buf^[2][j]<>'\') do dec(j);
- Delete(Buf^[2],1,j);
- j:=length(Buf^[3])-1; while (j>0) and (Buf^[3][j]<>'\') do dec(j);
- Delete(Buf^[3],1,j);
- if Buf^[3]=Buf^[2] then StrLwr(Buf^[3]);
- Buf^[1]:=Buf^[1]+Buf^[3];
- delete(S,1,i);
- end;
- until (not exists) or (S='');
- S:=Buf^[1]+S;
- Dispose(Buf);
- end else StrLwr(S);
- end; { CanonicalFname }
-
- function CanonicalFilename(fname: PChar): PChar;
- begin
- CanonicalFName(PChar2PString(fname)^);
- fname:=PString2PChar(PString(fname));
- CanonicalFilename:=fname;
- end;
-
- {=========================================================================}
- { BINARY AND TEXT FILE INPUT/OUTPUT ROUTINES. }
- { This set of routines is an interface between the LFN API and the Pascal }
- { style input/output routines. It uses ordinary text and file variables, }
- { storing special info in the UserData field. The variable is then fully }
- { compatible with the Pascal read(ln), write(ln), BlockRead, BlockWrite, }
- { etc input/output routines. }
- { All the functions return the DOS error code, and also put it into }
- { DOSERROR. The global "LFNRuntimeError" determines if runtime errors }
- { will be generated (by default, no.) }
- {=========================================================================}
-
- procedure LFNNew(var F; IsText: boolean);
- { This routine prepares a text or file variable for LFN use. It allocates }
- { memory for the long name, and initializes the entries in the UserData. }
- { It must be called before any other. }
- { The "IsText" flag tells if the variable is of type "file" or "text". }
- begin
- with PLFNFileParam(@F)^ do
- begin
- TextFile:=IsText;
- Initialized:=false;
- Magic:=LFNMagic;
- lfname:=Nil; plfname:=Nil;
- if LFNAble then
- begin
- GetMem(lfname,270); FillChar(lfname^,270,0);
- plfname:=PChar(@PByteArray(lfname)^[1]);
- end;
- end;
- end; { LFNNew }
-
- function LFNAssign(var F; name: string): integer;
- { This routine replaces the Pascal "Assign" routine. For existing files, }
- { it first determines the short name, and then invokes "Assign". If the }
- { file does not exist, it only stores the information in the UserData }
- { fields, since the equivalent short name is not known. The assign }
- { operation is then deferred to the first "LFNRewrite" call. }
- { LFNAssign may be called for the same variable for different filenames, }
- { so long as the type (file or text) is the same. }
- var
- tmp,fname: PString;
- IsText: boolean;
- P: PChar;
- begin
- if PLFNFileParam(@F)^.Magic<>LFNMagic then
- begin
- DosError:=LFNErr_NotAllocated;
- LFNAssign:=DosError;
- {$IFDEF WINDOWS}
- MessageBox(0,'Bug, LFNAssign',Nil,mb_ok); { for debugging }
- {$ENDIF}
- Exit;
- end;
- LFNAssign:=0; DosError:=0;
- if LFNAble then
- begin
- GetMem(fname,270);
- if LFNFileExist(name) then
- begin
- fname^:=LFNShortName(name);
- PByteArray(fname)^[length(fname^)+1]:=#0;
- end else fname^:='';
- end else fname:=@name;
- with PLFNFileParam(@F)^ do
- begin
- if fname^='' then Initialized:=false
- else begin
- IsText:=TextFile; tmp:=lfname; P:=plfname;
- if IsText then Assign(text(F),fname^) else assign(file(F),fname^);
- Initialized:=true;
- TextFile:=IsText; lfname:=tmp; plfname:=P;
- Magic:=LFNMagic;
- end;
- if LFNAble then
- begin
- lfname^:=name;
- PByteArray(lfname)^[length(lfname^)+1]:=#0;
- end;
- end;
- if LFNAble then FreeMem(fname,270);
- end; { LFNAssign }
-
- function LFNRewrite(var F; RecLen: word): integer;
- { This routine readies a file for output. If the file does not yet exist, }
- { it creates an empty file to get the system-determined short name, and }
- { performs a deferred Assign, since at Assign time a short name was not }
- { yet available (see description of LFNAssign). }
- { The routine returns 0 if successful, and the DOS errorcode if not. }
- var
- tmp,fname: PString;
- IsText: boolean;
- P: PChar;
-
- function Err(e: byte): byte;
- begin
- LFNRewrite:=e; DosError:=e; Err:=e;
- if LFNRuntimeErrors and (e<>0) then RunError(e);
- end;
-
- begin
- Err(0);
- if PLFNFileParam(@F)^.Magic<>LFNMagic then
- begin
- {$IFDEF WINDOWS}
- MessageBox(0,'Bug, LFNRewrite',Nil,mb_ok); { for debugging }
- {$ENDIF}
- Err(LFNErr_NotAllocated); Exit;
- end;
- if LFNAble then
- with PLFNFileParam(@F)^ do
- begin
- if not Initialized then { create the file, so we can get a valid short name }
- begin
- if Err(LCreateEmpty(plfname))=0 then
- begin
- New(fname);
- fname^:=LFNShortName(lfname^);
- IsText:=TextFile; tmp:=lfname; P:=plfname;
- if IsText then Assign(text(F),fname^) else assign(file(F),fname^);
- Initialized:=true;
- TextFile:=IsText; lfname:=tmp; plfname:=P;
- Magic:=LFNMagic;
- end;
- end;
- if Initialized then
- begin
- {$I-}
- if TextFile then Rewrite(text(F))
- else if RecLen=0 then Rewrite(file(F))
- else Rewrite(file(F),RecLen);
- Err(IoResult);
- {$I+}
- end;
- end else with PLFNFileParam(@F)^ do
- if Initialized then
- begin
- {$I-}
- if TextFile then Rewrite(text(F))
- else if RecLen=0 then rewrite(file(F))
- else Rewrite(file(F),RecLen);
- Err(IoResult);
- {$I+}
- end;
- end; { LFNRewrite }
-
- function LFNAppend(var F; RecLen: word): integer;
- { This routines opens a previously LFNAssigned for output at the EOF. }
- { Its not really necessary, except that it performs additional error }
- { checking to make sure that the file was properly initialized. }
- { Also, in contrast to the TP Append, if the file does not exist the }
- { routine calls LFNRewrite to create and open it. }
- { The routine returns 0 if successful, and the DOS errorcode if not. }
-
- function Err(e: byte): byte;
- begin
- LFNAppend:=e; DosError:=e; Err:=e;
- if LFNRuntimeErrors and (e<>0) then RunError(e);
- end;
-
- begin
- Err(0);
- if PLFNFileParam(@F)^.Magic<>LFNMagic then
- begin
- Err(LFNErr_NotAllocated); Exit;
- end;
- with PLFNFileParam(@F)^ do
- begin
- if Magic<>LFNMagic then
- begin
- Err(LFNErr_NotAllocated); Exit;
- end else if not TextFile then
- begin
- Err(LFNErr_NotATextFile); Exit;
- end else if not Initialized then Err(LFNRewrite(F,RecLen))
- else begin
- {$I-}
- Append(text(F)); Err(IoResult);
- {$I+}
- end;
- end;
- end; { LFNAppend }
-
- function LFNReset(var F; RecLen: word): integer;
- { This routines opens a file for input, instead of "reset". Its not really }
- { necessary, except that it performs additional error checking to make }
- { sure that the file was properly initialized. }
- { The routine returns 0 if successful, and the DOS errorcode if not. }
-
- procedure Err(e: byte);
- begin
- LFNReset:=e; DosError:=e;
- if LFNRuntimeErrors and (e<>0) then RunError(e);
- end;
-
- begin
- Err(0);
- if PLFNFileParam(@F)^.Magic<>LFNMagic then
- begin
- {$IFDEF WINDOWS}
- MessageBox(0,'Bug, LFNReset',Nil,mb_ok); { for debugging }
- {$ENDIF}
- Err(LFNErr_NotAllocated); Exit;
- end;
- with PLFNFileParam(@F)^ do
- begin
- if not Initialized then LFNReset:=LFNErr_UnInitialized
- else begin
- {$I-}
- if TextFile then Reset(text(F))
- else if RecLen=0 then Reset(file(F))
- else Reset(file(F),RecLen);
- Err(IoResult);
- {$I+}
- end;
- end;
- end; { LFNReset }
-
- function LFNErase(var F): integer;
- { This routines erases a previously LFNAssigned, but not opened, file. }
- { Its not really necessary, except that it performs additional error }
- { checking to make sure that the file was properly initialized. Also, }
- { it re-assignes the file so it will be properly ready for a rewrite. }
- { The routine returns 0 if successful, and the DOS errorcode if not. }
- var
- S: PString;
- S1: PChar;
-
- function Err(e: byte): byte;
- begin
- LFNErase:=e; DosError:=e; Err:=e;
- if LFNRuntimeErrors and (e<>0) then RunError(e);
- end;
-
- begin
- with PLFNFileParam(@F)^ do
- begin
- LFNErase:=0;
- if (Magic<>LFNMagic) then
- begin
- Err(LFNErr_NotAllocated); Exit;
- end else if (not Initialized) then
- begin
- Err(LFNErr_UnInitialized); Exit;
- end;
- LFNClose(F);
- if not LFNAble then
- begin
- GetMem(S,81); S1:=PChar(@PByteArray(S)^[1]);
- Move(SName,S1^,80); S^:=Chr(StrLen(S1));
- end;
- {$I-}
- if TextFile then Erase(text(F)) else Erase(file(F));
- if Err(IoResult)=0 then
- begin
- if LFNAble then LFNAssign(F,lfname^)
- else begin
- LFNAssign(F,S^); FreeMem(S,81);
- end;
- end;
- {$I+}
- end;
- end; { LFNErase }
-
- function LFNClose(var F): integer;
- { This routines closes a previously LFNAssigned and opened file. }
- { Its not really necessary, except that it performs additional error }
- { checking to make sure that the file was properly initialized. }
- { The routine returns 0 if successful, and the DOS errorcode if not. }
-
- function Err(e: byte): byte;
- begin
- LFNClose:=e; DosError:=e; Err:=e;
- if LFNRuntimeErrors and (e<>0) then RunError(e);
- end;
-
- begin
- Err(0);
- with PLFNFileParam(@F)^ do
- begin
- if Magic<>LFNMagic then
- begin
- Err(LFNErr_NotAllocated); Exit;
- end else if not Initialized then
- begin
- Err(LFNErr_UnInitialized); Exit;
- end;
- {$I-}
- if TextFile then close(text(F)) else close(file(F));
- Err(IoResult);
- {$I+}
- end;
- end; { LFNClose }
-
- procedure LFNDispose(var F);
- { This routine disposes of the additional memory allocated by LFNNew, }
- { and cleans up the UserData fields. If the file is open, it also }
- { closes it, so that there is no need to call LFNClose previously. }
- begin
- with PLFNFileParam(@F)^ do
- begin
- if (Magic<>LFNMagic) or (not Initialized) then Exit;
- LFNClose(F);
- if lfname<>Nil then FreeMem(lfname,270);
- lfname:=Nil; plfname:=Nil; Initialized:=false; Magic:='';
- end;
- end; { LFNDispose }
-
- function LFNRename(var F; NewName: string): integer;
- { This routines renames a previously LFNAssigned, but not opened, file. }
- { The file variable is then re-assigned to the new name. }
- { The routine returns 0 if successful, and the DOS errorcode if not. }
- var
- i,len: integer;
-
- function Err(e: byte): byte;
- begin
- LFNRename:=e; DosError:=e; Err:=e;
- if LFNRuntimeErrors and (e<>0) then RunError(e);
- end;
-
- begin
- Err(0);
- if NewName='' then Exit;
- with PLFNFileParam(@F)^ do
- begin
- if Magic<>LFNMagic then
- begin
- Err(LFNErr_NotAllocated); Exit;
- end else if not Initialized then
- begin
- Err(LFNErr_UnInitialized); Exit;
- end;
- if not LFNAble then { The usual TP stuff }
- begin
- {$I-}
- if TextFile then Rename(text(F),NewName) else Rename(file(F),NewName);
- Err(IoResult);
- {$I+}
- end else { LFN }
- begin
- len:=length(NewName);
- for i:=1 to len do NewName[i-1]:=NewName[i]; NewName[len]:=#0;
- if Err(LRenameFile(plfname,PChar(@NewName)))=0 then
- begin
- for i:=len downto 1 do
- NewName[i]:=NewName[i-1]; NewName[0]:=chr(len);
- LFNAssign(F,NewName);
- end;
- end;
- end;
- end; { LFNRename }
-
- begin
- LFNAble:=SupportsLFN;
- end.